/* 
 *  File: Data.java
 *  Author: Edson Pereira
 *  Version: 1.0
 *  Date: 02/10/2011
 *  
 *  Oracle Certified Master Java SE 6 Developer
 *  
 *  This class is part of Java Developer Assignment CX-310-252A_1
 */


package suncertify.db;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * This class provide main functions to access database
 * <p>
 * This class is a facade that access database file and control all access from
 * clients request, Network clients and stand alone clients.
 * 
 * @author Edson Pereira
 * @version 1.0
 * @see DataProvider
 */
public class Data implements DB {

    private DataProvider dataProvider;
    private final Map<Integer, Long> dataLocked;
    private String path;

    /** 
     * Creates an instance of Data to access database receiving a path as 
     * parameter
     * 
     *  @param path Path of database file
     *  @throws FileNotFoundException Indicates the database was not found
     *  @throws IOException Indicates fail or interrupted I/O operation
     */
    public Data(String path) throws FileNotFoundException, IOException{
        this.path = path;
        dataProvider = new DataProvider(path);
        dataLocked = new HashMap<Integer, Long>();
    }

    /** 
     * Reads a record from file. 
     *
     * @param recNo Record number to be read from file
     * @return Returns an array where each element is a record value
     * @throws RecordNotFoundException Indicates record number does not 
     *         exist or was marked as deleted.
     */
	public String[] read(int recNo) throws RecordNotFoundException{
	    String[] result = null;
	    synchronized(dataProvider){
	        result = dataProvider.read(recNo);
	    }
		return result;
	}

    /**
     * Modifies fields of a record. 
     * <p>The new value for field n appears in data[n].
     *
     * @param recNo Record number to be read from file
     * @param data Array of {@code String} where each element is a record
     *        value
     * @param lockCookie Owner of record operation
     * @throws RecordNotFoundException Indicates record number does not exist 
     *         or marked as deleted.
     * @throws SecurityException Record is locked with a cookie other than 
     *         lockCookie.
     */
	public void update(int recNo, String[] data, long lockCookie)
			throws RecordNotFoundException, SecurityException {
	    synchronized(dataProvider){
    	    if(dataLocked.containsKey(recNo)){
    	        if (dataLocked.get(recNo) == lockCookie){
    	            dataProvider.update(recNo, data);
    	        }else{
    	            throw new SecurityException();
    	        }
    	    }else{
    	        throw new RecordNotFoundException();
    	    }
	    }
	}

    /**
     * Deletes a record making the record number and associated disk storage
     * available for reuse.
     *
     * @param recNo Record number to be read from file
     * @param lockCookie Owner of record operation
     * @throws RecordNotFoundException Indicates record number does not exist 
     *         or marked as deleted.
     * @throws SecurityException Record is locked with a cookie other than 
     *         lockCookie.
     */
	public void delete(int recNo, long lockCookie)
			throws RecordNotFoundException, SecurityException {
	    synchronized(dataProvider){
	       if(dataLocked.containsKey(recNo)){
	            if (dataLocked.get(recNo) == lockCookie){
	                dataProvider.delete(recNo);
	            }else{
	                throw new SecurityException();
	            }
	        }else{
	            throw new RecordNotFoundException();
	        }
	    }
	}

    /**
     * Search for records that match the specified criteria. 
     *
     * <p>Field {@code n} in the database file is described by criteria
     *    [{@code n}]. A null 
     * value in criteria[{@code n}] matches any field value. 
     * <p>A non-null value in criteria[{@code n}] matches any field value that
     * begins with criteria[{@code n}].
     * For example, "Fred" matches "Fred" or "Freddy".) 
     *
     * @param criteria Array of {@code String} where each element is a record
     *        value
     * @return An array of record numbers that match the specified criteria. 
     */
	public int[] find(String[] criteria) {
	    int[] result = null;
	    synchronized(dataProvider){
	        result = dataProvider.find(criteria);
	    }
	    return result;
	}

    /**
     * Creates a new record in the database
     * @param data Array of {@code String} where each element is a record
     *        value
     * @return The {@code int} record number of the new record
     * @throws DuplicateKeyException Record already created
     */
	public int create(String[] data) throws DuplicateKeyException {
	    int result = 0;
	    synchronized(dataProvider){
	        result = dataProvider.create(data);
	    }
		return result;
	}

    /**
     * Locks a record so that it can only be updated or deleted by this client.
     * @param recNo Record number to be read from file
     * @return A {@code long} id owner that lock record
     * @throws RecordNotFoundException Indicates record number does not exist 
     *         or marked as deleted.
     */
     public synchronized long lock(int recNo) throws RecordNotFoundException {
        long lockId = Thread.currentThread().getId();

        while (dataLocked.containsKey(recNo)){
            try{
                wait();
            }catch (InterruptedException exception) {
            }
        }
        dataLocked.put(recNo, lockId);

	    return lockId;
	}

    /** 
     * Releases the lock on a record. 
     * @param recNo Record number to be read from file
     * @param cookie Owner long id of lock record
     * @throws RecordNotFoundException Indicates record number does not exist or
     *         marked as deleted.
     * @throws SecurityException Record is locked with a cookie other than 
     *         lockCookie.
     */ 
	public synchronized void unlock(int recNo, long cookie) throws 
	        RecordNotFoundException,SecurityException {

	    if (dataLocked.containsKey(recNo)){
	        if (dataLocked.get(recNo) == cookie){
	            dataLocked.remove(recNo);
	            try{
	                notifyAll();
	            }catch(IllegalMonitorStateException exception){
	            }
	        }else{
	            throw new SecurityException();
	        }
	    }else{
	        throw new RecordNotFoundException();
	    }
    }
}
